/*
	Colored Trails

	Copyright (C) 2006, President and Fellows of Harvard College.  All Rights Reserved.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package edu.harvard.eecs.airg.coloredtrails.shared.types;

import edu.harvard.eecs.airg.coloredtrails.shared.ObservableInitializableObject;
import edu.harvard.eecs.airg.coloredtrails.shared.Scoring;
import edu.harvard.eecs.airg.coloredtrails.server.ServerPhases;

import java.io.Serializable;
import java.util.Observer;
import java.util.Observable;
import java.util.LinkedHashSet;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Collection;

/**
<b>Description</b>
Represents the state of a game instance.

<p>

<b>Issues</b>

<p>

<b>Observers</b>
The subclass ServerGameStatus is observed by GamesStatusSender; the
registration is done in server.ServerData.addNewGame().
[ADD NOTES on observers of the subclass ClientGameStatus.]

<p>

<b>Notifications</b>

<table width="100%" cellpadding=10 bgcolor=silver>
    <tr><td>Method</td><td>Message Sent</td></tr>
    <tr><td>setBoard(...)</td><td>BOARD_CHANGED</td></tr>
	<tr><td>setPlayers(...)</td><td>PLAYERS_CHANGED</td></tr>
	<tr><td>setPhases(...)</td><td>PHASES_CHANGED</td></tr>
	<tr><td>setLog(...)</td><td>LOG_CHANGED</td></tr>
	<tr><td>setStarted()</td><td>GAME_START</td></tr>
	<tr><td>setEnded()</td><td>GAME_END</td></tr>
</table>

Observers are notified when GameStatus receives the following notifications in method 'update()':

<table width="100%" cellpadding=10 bgcolor=silver>
    <tr><td>Message In</td><td>Message Out</td></tr>
    <tr><td>BOARD_CHANGED</td><td>BOARD_CHANGED</td></tr>
	<tr><td>PLAYER_CHANGED</td><td>PLAYER_CHANGED</td></tr>
	<tr><td>PHASE_ADVANCED</td><td>PHASE_ADVANCED</td></tr>
	<tr><td>LOG_CHANGED</td><td>LOG_CHANGED</td></tr>
</table>

The subclass ClientGameStatus sends NEXT_PHASE notification when its
notifyObserversPhaseAdvanced() method is called.

<p>

<b>Classes Observed</b>
The GameStatus instance observes instances of:  PlayerStatus, HistoryLog,
Phases; subclass ServerGameStatus registers as an observer of Board.

<table width="100%" cellpadding=10 bgcolor=silver>
    <tr><td>Message Checked</td><td>Class of Message Origin</td></tr>
    <tr><td>BOARD_CHANGED</td><td>types.Board</td></tr>
	<tr><td>PLAYER_CHANGED</td><td>types.PlayerStatus</td></tr>
	<tr><td>PHASE_ADVANCED</td><td>types.Phases</td></tr>
	<tr><td>LOG_CHANGED</td><td>types.HistoryLog</td></tr>
</table>

<p>

<b>Future Development</b>
The GameStatus object updates its player list as a whole; that is,
the current list is replaced by a new list.
<p>
[STATE where this list is first made -- in configuration class?]
<p>
In contrast, we can imagine methods to add and remove individual players;
such methods are merely a convenience to avoid having to rebuild a player
list, though these methods might be more appropriately placed somewhere
other than this class.  The notification "PLAYERS_CHANGED" would still
be appropriate.

<p>

<b>Original Summary</b>
 * A game type describing the overall status of a single game as a whole.
 * Contains many of the other game types.
 *
 * @author Paul Heymann (ct3@heymann.be)
	@author Sevan G. Ficici (class-level review and comments)
 */
public class GameStatus extends ObservableInitializableObject
        implements Observer, Serializable {

	/** [name of configuration class associated with game?] */
    protected String configName;
	/** set of player states */
    protected Set<PlayerStatus> players = new HashSet<PlayerStatus>();
	/** the game board */
    protected Board board = new Board(1, 1);
	/** the ID of this game instance; gameId is generated by server */
    protected int gameId = -1;
	/** the phases of this game */
    protected Phases phases;// = new Phases();
	/** the history log for this game */
    protected HistoryLog log = new HistoryLog();

    protected Scoring score = null;

    /** the Game Palette for this game (Monira 2-8-2006 */
    protected GamePalette gamePalette = new GamePalette();

    protected boolean isStarted = false;
    protected boolean isInitialized = false;

    /************************
		CONSTRUCTORS
	************************/

    public GameStatus() {
        /* should only use this with fromHash() or when setting
           values manually */

        log.addObserver(this);
    }


    // 2-9-2006 Monira : Adding game pallete
    public GameStatus(String configName, Collection players, int gameID, GamePalette gamePalette){
        this();
        //setPrivateValues(configName, new ArrayList<Integer>(players), gameID,gamePalette);
        this.configName = configName;
        this.players = new HashSet<PlayerStatus>(players);
        this.gameId = gameID;
        this.gamePalette = new GamePalette(gamePalette);
        setInitialized(true);
    }

    public GameStatus(GameStatus g){
        this(g.configName, g.players, g.gameId, g.gamePalette);
    }

    /**
     * Get the current board configuration of the game.
     *
     * @return The board status of the game.
     */
    public Board getBoard() {
        return board;
    }

     /**
     * Get the current game palette configuration of the game.
     *
     * @return The game palette of the game.
     *
     * Written By: Monira Sarne 2-15-06
     */
    public GamePalette getGamePalette() {
        return gamePalette;
    }


    /**
     * Set the current board configuration of the game.
     *
     * @param board The board status of the game.
     */
    public void setBoard(Board board) {
        this.board = board;

        //board.addObserver(this);
        setChanged();
        notifyObservers("BOARD_CHANGED");
    }

    /**
     * Set the current game palette of the game.
     *
	 <p>
	 * @param gamePaletteParam The game palette of the game.
     * Written by: Monira Sarne 2-15-06
     */
    public void setGamePalette(GamePalette gamePaletteParam) {
      this.gamePalette =  gamePaletteParam;
      setChanged();
      notifyObservers("GAME_PALETTE_CHANGE");
    }

    /**
     * Set the game to a started state.
     */
    public void setStarted() {
        isStarted = true;
        setChanged();
        notifyObservers("GAME_START");
    }

    /**
     * Notify observers that game has been initialized by game configuration class run() method
     * @author sevan
     */
    public void setInitialized()
    {
    	isInitialized = true;
    	setChanged();
    	notifyObservers("GAME_INITIALIZED");
    }

    /**
     * Set the game to an ended state.
     */
    public void setEnded() {
        isStarted = false;
        isInitialized = false;
        setChanged();
        notifyObservers("GAME_END");
    }

    /**
     * Set the log to a new history log.
     *
     * @param log The new history log.
     */
    public void setHistoryLog(HistoryLog log) {
    	if( this.log != null )
    		this.log.deleteObservers();
    	
        this.log = log;
        log.addObserver(this);
        setChanged();
        notifyObservers("LOG_CHANGED");
    }

    /**
     * Get the current set of phases associated with the game.
     *
     * @return The phases associated with the game.
     */
    public Phases getPhases() {
        return phases;
    }

    /**
     * Set the current set of phases associated with the game.
     *
     * @param phases The new phases associated with the game.
     */
    public void setPhases(Phases phases) {

	    System.out.println( "setPhases in GameStatus");
	    
	    if( phases != null )
	        phases.deleteObserver( this );
        this.phases = phases;
	    //phases.resetTimer();
        phases.addObserver(this);
        setChanged();
        notifyObservers("PHASES_CHANGED");
    }

    /**
     * Set the participating players in the game.
     *
     * @param al A list of players designated to participate in the game.
     */
    public void setPlayers(Set<PlayerStatus> al) {
    	//System.out.println( "Setting players" + al.size() );
    	Set<PlayerStatus> oldPlayers = players;
        
        players = al;
        //System.out.println( "A " + players.size() );
        for (PlayerStatus p : players) {
            p.addObserver(this);
        }
        //System.out.println( "B " + players.size() );
        doAllInitializedCheck();
        //System.out.println( "C " + players.size() );
        setChanged();
        //System.out.println( "D " + players.size() );
        notifyObservers("PLAYER_CHANGED");
        //System.out.println( "E " + players.size() );
        
    	if( oldPlayers != null ) {
	        for (PlayerStatus p : oldPlayers) {
	            p.deleteObservers();
	        }
	        //oldPlayers.clear();
    	}
        //System.out.println( "F " + players.size() );

    	//System.out.println( "end Setting players" + players.size() );
    }

    public Scoring getScoring() {
        return score;
    }

    public void setScoring( Scoring scoring ) {
        this.score = scoring;
        setChanged();
        notifyObservers("SCORING_CHANGED");
    }

    public void update(Observable observed, Object notification) {
        String noteString = (String) notification;

        if (noteString.equals("BOARD_CHANGED")) {
            setChanged();
            notifyObservers("BOARD_CHANGED");
        } else if (noteString.equals("PLAYER_CHANGED")) {
            setChanged();
            notifyObservers("PLAYER_CHANGED");
        } else if (noteString.equals("PHASE_ADVANCED")) {
            setChanged();
            notifyObservers("PHASE_ADVANCED");
        } else if (noteString.equals("LOG_CHANGED")) {
            setChanged();
            notifyObservers("LOG_CHANGED");
        } else if (noteString.equals("GAME_PALETTE_CHANGE")) {
            setChanged();
            notifyObservers("GAME_PALETTE_CHANGE");
        } else if (noteString.equals("SCORING_CHANGED")) {
            setChanged();
            notifyObservers("SCORING_CHANGED");
        } else {
            System.out.println( "Warning: Change in GameStatus update not caught. " + noteString );
        }
    }

    /**
     * Determine whether the game is started.
     *
     * @return Whether the game is started.
     */
    public boolean isStarted() {
        return isStarted;
    }

    /**
     * Returns true if game has been initialized
     */
    public boolean isInitialized()
    {
    	return isInitialized;
    }

    /**
     * Get a list of players participating in the game.
     *
     * @return A list of players participating in the game.
     */
    public Set<PlayerStatus> getPlayers() {
        return players;
    }

    /**
     * Get the log of actions which have occurred in the game thus far.
     *
     * @return The log of actions which have occurred in the game thus far.
     */
    public HistoryLog getHistoryLog() {
        return log;
    }

    /*protected void setPrivateValues(String configName, Collection players,
                                    int gameID, GamePalette gamePalette) {
        this.configName = configName;
        this.players = new HashSet<PlayerStatus>(players);
        this.gameId = gameID;
        this.gamePalette = new GamePalette(gamePalette);
        setInitialized(true);
    } */

    protected void doAllInitializedCheck() {
        if ((configName.length() > 0) &&
                (gameId != -1)) {
            setInitialized(true);
        }
    }



    /**
     * Get the game id of this game.
     *
     * @return The game id of this game.
     */
    public int getGameId() {
        throwIfNotInitialized();
        return gameId;
    }

    /**
     * Get a player given that players per game id.
     *
     * @param id The per game id of the player.
     * @return The player object associated with the per game id in this
     *         game.
     */
    public PlayerStatus getPlayerByPerGameId(int id) {
        for (PlayerStatus p : players) {
            if (p.getPerGameId() == id) {
                return p;
            }
        }
        return null;
    }

    public String toString() {

        String playersString = "Game Players: {";
        for (PlayerStatus in : players) {
            playersString = playersString + in.getPin() + "," + " pergameid: " + in.getPerGameId() + " ,";
        }
        playersString = playersString + "}\n";
        return "Configuration Name: {" + configName + "}\n" +
                "Game ID: {" + gameId + "}\n" + playersString;
    }

    /**
     * Get the set of the players with the given team id
     * @param teamId Integer representing a team
     * @return Set of PlayerStatus with the team id "teamId"
     * @author ilke
     */
    public Set<PlayerStatus> getTeam(int teamId) {
    	LinkedHashSet<PlayerStatus> team = new LinkedHashSet<PlayerStatus>();
    	if(teamId != -1) {
			for(PlayerStatus ps:players) {
				if(ps.getTeamId() == teamId) {
					team.add(ps);
				}
			}
    	}
    	return team;

    }
}
